package com.naiterui.ehp.bp.security.jwt;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.security.properties.SecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @author /
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {

  private final SecurityProperties properties;
  private final UserDetailsService userDetailsService;
  public static final String AUTHORITIES_KEY = "auth";

  private JwtParser jwtParser;
  private JwtBuilder jwtBuilder;

  @Override
  public void afterPropertiesSet() {
    byte[] keyBytes = Decoders.BASE64.decode(this.properties.getBase64Secret());
    Key key = Keys.hmacShaKeyFor(keyBytes);
    this.jwtParser = Jwts.parserBuilder().setSigningKey(key).build();
    this.jwtBuilder = Jwts.builder().signWith(key, SignatureAlgorithm.HS512);
  }

  /**
   * 创建Token 设置永不过期， Token 的时间有效性转到Redis 维护
   *
   * @param authentication /
   * @return /
   */
  public String createToken(Authentication authentication) {
    /*
     * 获取权限列表
     */
    String authorities = authentication.getAuthorities().stream()
        .map(GrantedAuthority::getAuthority)
        .collect(Collectors.joining(","));

    return this.jwtBuilder
        // 加入ID确保生成的 Token 都不一致
        .setId(IdUtil.simpleUUID())
        .claim(AUTHORITIES_KEY, authorities)
        .setSubject(authentication.getName())
        .compact();
  }

  /**
   * 依据Token 获取鉴权信息
   *
   * @param token /
   * @return /
   */
  Authentication getAuthentication(String token) {
    Claims claims = this.getClaims(token);
    UserDetails userDetails = this.userDetailsService.loadUserByUsername(claims.getSubject());
    return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
  }

  public Claims getClaims(String token) {
    return this.jwtParser
        .parseClaimsJws(token)
        .getBody();
  }

  /**
   * @param token 需要检查的token
   */
  public void checkRenewal(String token) {
    // 判断是否续期token,计算token的过期时间
    long time = RedisUtil.keyOps().getExpire(this.properties.getOnlineKey() + token) * 1000;
    Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
    // 判断当前时间与过期时间的时间差
    long differ = expireDate.getTime() - System.currentTimeMillis();
    // 如果在续期检查的范围内，则续期
    if (differ <= this.properties.getDetect()) {
      long renew = time + this.properties.getRenew();
      RedisUtil.keyOps().expire(this.properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
    }
  }

  public String resolveToken(HttpServletRequest request) {
    String bearerToken = request.getHeader(this.properties.getHeader());
    if (StringUtils.isEmpty(bearerToken)) {
      bearerToken = request.getParameter(this.properties.getHeader());
    }
    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(this.properties.getTokenStartWith())) {
      // 去掉令牌前缀
      return bearerToken.replace(this.properties.getTokenStartWith(), "");
    } else {
      log.debug("非法Token：{}", bearerToken);
    }
    return null;
  }

}
